package br.com.senac.pi.persistence;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import br.com.senac.pi.interfaces.PIDataObject;
import br.com.senac.pi.util.FilesType;

public class PersistenceModule {

	List<PIDataObject> objetos = null;
	Map<String, File> arquivos = null;
	Map<String, Long> indices = null;
	
	public PersistenceModule() {
		objetos = new ArrayList<PIDataObject>();
		arquivos = new HashMap<String, File>();
		indices = new HashMap<String, Long>();
	}
	
	/**
	 * Checa se o arquivo de indices existe, se não existir, criar o arquivo
	 * @return
	 */
	private boolean checkIndexFile() {
		File file = new File("./base/index.txt");
		BufferedWriter writer = null;
		// Se o arquivo de persistencia não existir, é criado
		try {
			if (!file.exists()) {
				System.out.println("Arquivo de indices nao encontrado. Criando novo aquivo...");
				writer = new BufferedWriter(new FileWriter(file));
				
				for (FilesType fileFilesType : FilesType.values()) {
					writer.write(fileFilesType.getNome() + "=" + 1 + "\n");
				}
			}
			
			arquivos.put("Index", file);
		} catch (IOException e) {
			System.out.println("Arquivo de indices nao encontrado.");
			return false;
		} finally {
			try {
				if (writer != null)
					writer.close();
			} catch (IOException e) {				
				e.printStackTrace();
			}
		}
		
		return true;
	}
	
	/**
	 * Executa os passos de veficacao dos arquivos, necessario ser executado na inicializacao
	 * do aplicativo
	 */
	public void loadBase() {
		checkIndexFile();
		loadIndexFile();
		
		Properties prop = new Properties();
		try {
			prop.load(new FileInputStream("./persistence.properties"));
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		String base = prop.getProperty("folder.data");
		
		// Carrega as classes do modelo
		try {
			for (FilesType type : FilesType.values()) {
				PIDataObject data = (PIDataObject) Class.forName(type.getObjeto()).newInstance();
				objetos.add(data);
			}
		} catch (InstantiationException e) {
			System.out.println("Erro ao carregar classes do modelo;");
		} catch (IllegalAccessException e) {
			System.out.println("Erro ao carregar classes do modelo;");
		} catch (ClassNotFoundException e) {
			System.out.println("Erro ao carregar, classe do modelo nao encontrada;");
		}
		
		try {
			// Checa os arquivos, se os arquivos não existirem, são criados
			for (PIDataObject object : objetos) {
				System.out.println("Checando arquivo: " + object.getFileType().getNome());
				File file = new File("./" + base + "/" + object.getFileType().getNome() + ".txt");
				// Se o arquivo de persistencia não existir, é criado
				if (!file.exists()) {
					System.out.println("Arquivo: " + object.getFileType().getNome() + ", nao encontrado. Criando novo aquivo...");
					file.createNewFile();					
				} 
				
				arquivos.put(object.getFileType().getNome(), file);
			}
		} catch (IOException e) {
			System.out.println("Arquivo de persistencia nao pode ser criado.");
		}
	}
	
	/**
	 * Grava os dados no arquivo compativel com o dado do modelo
	 * @param file arquivo onde o dado vai ser gravado
	 * @param obj objeto que vai ser gravado
	 * @throws FileNotFoundException Se nao encontrar o arquivo retorna excecao
	 */
	private void writeData(File file, PIDataObject obj) throws FileNotFoundException {
		BufferedWriter writer = null;
		try {
			writer = new BufferedWriter(new FileWriter(file, true));
			writer.write(obj.persistenceData() + "\n");			
		} catch (FileNotFoundException e) {
			throw new FileNotFoundException("Arquivo não encontrado.");
		} catch (IOException e) {
			throw new FileNotFoundException("Erro ao escrever no arquivo de " + obj.getFileType());
		} finally {
			try {
				if (writer != null)				
					writer.close();
			} catch (IOException e) {
				throw new FileNotFoundException("Erro ao fechar aquivo " + obj.getFileType());
			}		
		}
	}

	/**
	 * Carrega o arquivo de indices
	 */
	private void loadIndexFile() {
		try {
			Properties indexs = new Properties();
			indexs.load(new FileInputStream(arquivos.get("Index")));
			
			for (FilesType type : FilesType.values()) {
				String value = indexs.get(type.getNome()).toString();
				indices.put(type.getNome(), new Long(value));
			}			
		} catch (FileNotFoundException e) {
			System.out.println("Arquivo de indices não encontrado.");
		} catch (IOException e) {
			System.out.println("Erro ao ler o arquivo de indices.");
		}
	}
	
	/**
	 * Recuperar o indice especifico para o dado do modelo
	 * @param type tipo do modelo
	 * @return o numero do indice para ser inserido no arquivo
	 * @throws IOException
	 */
	private Long getIndice(FilesType type) throws IOException {
		Long result = indices.get(type.getNome());
		incrementIndice(type);
		return result;
	}
	
	/**
	 * Incrementa o número do indice após utilização
	 * @param type
	 * @throws IOException
	 */
	private void incrementIndice(FilesType type) throws IOException {
		BufferedWriter writer = null;
		try {
			writer = new BufferedWriter(new FileWriter(new File("./base/index.bak")));
			Long indice = indices.get(type.getNome());
			indices.put(type.getNome(), ++indice);
			
			for (FilesType fileType : FilesType.values()) {
				writer.append(fileType.getNome() + "=" + indices.get(fileType.getNome()) + "\n");
			}
		} catch (Exception e) {
			System.out.println("Erro ao atualizar o arquivo de indices.");
		} finally {
			if (writer != null) {
				writer.close();
			}
		}		
		flushNewFile("Index");
	}
	
	/**
	 * Transforma o arquivo de backup, criado apos uma alteração, no arquivo de base
	 * @param nome nome do arquivo que vai ser alterado
	 */
	private void flushNewFile(String nome) {
		InputStream in = null;
		OutputStream out = null;
		
		File bak = new File("./base/" + nome + ".bak");
		File orig = new File("./base/" + nome + ".txt");
		
		try {
			if (orig.exists()) {
				orig.delete();
			}
			
			orig = new File("./base/" + nome + ".txt");
			
			in = new FileInputStream(bak);
			out = new FileOutputStream(orig);
			
			byte[] buffer = new byte[1024];
			
			int length;
			
			while ((length = in.read(buffer)) > 0) {
				out.write(buffer, 0, length);				
			}
			
			in.close();
			out.close();
			
			bak.delete();
		} catch (IOException e) {
			System.out.println("Erro ao atualizar arquivo de " + nome);
		}		
				
	}
	
	/**
	 * Não utilizado 
	 * @param object
	 */
	private void flushNewData(PIDataObject object) {
		BufferedWriter out = null;
		
		File file = new File("./base/" + object.getFileType().getNome() + ".txt");
		
		try {
			out = new BufferedWriter(new FileWriter(file));
			out.write(object.persistenceData() + "\n");
		} catch (IOException e) {
			System.out.println("Erro ao gravar os dados no arquivo." + object.persistenceData());
		}
				
	}
	
	/**
	 * Grava os dados no arquivo de backup
	 * @param list lista de dados que vao ser gravados 
	 * @param type tipo do arquivo no modelo
	 * @throws IOException
	 */
	private void flushNewDataList(List<String> list, FilesType type) throws IOException {
		BufferedWriter out = null;
		
		File file = new File("./base/" + type.getNome() + ".bak");
		
		try {
			out = new BufferedWriter(new FileWriter(file));
			for (String string : list) {
				out.write(string + "\n");	
			}						
		} catch (IOException e) {
			System.out.println("Erro ao gravar os dados no arquivo " + type.getNome());
		} finally {
			if (out != null) {
				out.close();
			}
		}
				
	}
	
	/**
	 * Grava novo dados no arquivo do modelo
	 * @param obj Dado que vai ser gravado no modelo
	 * @return retorno o identificador do dado gravado
	 * @throws FileNotFoundException
	 */
	public Long persistData(PIDataObject obj) throws FileNotFoundException {
		Long result = null;
		try {
			obj.setId(getIndice(obj.getFileType()));
			writeData(arquivos.get(obj.getFileType().getNome()), obj);
			result = obj.getId();
			
		} catch (Exception e) {
			System.out.println("Erro ao gravar dados.");
		}
		
		return result;
	}
	
	/**
	 * Procura o dado compativel com o que deve ser removido do arquivo e remove.
	 * @param obj Dado que deve ser removido.
	 * @throws FileNotFoundException
	 */
	public void remove(PIDataObject obj) throws FileNotFoundException {
		String line = "";
		List<String> lista = new ArrayList<String>();
		BufferedReader reader = null;
		
		try {
			reader = new BufferedReader(new FileReader(new File("./base/" + obj.getFileType().getNome() + ".txt")));
			while ((line = reader.readLine()) != null) {
				if (!line.toUpperCase().equals(obj.persistenceData().toUpperCase())) {
					lista.add(line);
				}
			}
		} catch (FileNotFoundException e) {
			throw new FileNotFoundException("remove: Arquivo não encontrado.");
		} catch (IOException e) {
			throw new FileNotFoundException("remove: Erro ao escrever no arquivo de " + obj.getFileType());
		} finally {
			try {
				if (reader != null)				
					reader.close();
			} catch (IOException e) {
				throw new FileNotFoundException("remove: Erro ao fechar aquivo " + obj.getFileType());
			}		
		}	
		
		try {
			flushNewDataList(lista, obj.getFileType());
			flushNewFile(obj.getFileType().getNome());	
		} catch (IOException e) {
			throw new FileNotFoundException("remove: Erro ao fechar aquivo " + obj.getFileType());
		}		
	}
	
	/**
	 * Recupera a lista dos dados contidos em um arquivo do modelo
	 * @param type Tipo do arquivo cujos dados devem ser retornados
	 * @return Lista com os dados do arquivo
	 * @throws FileNotFoundException
	 */
	public List<String> listData(FilesType type) throws FileNotFoundException {
		String line = null;
		List<String> result = null;
		BufferedReader reader = null;
		
		try {
			result = new ArrayList<String>();
			reader = new BufferedReader(new FileReader(new File("./base/" + type.getNome() + ".txt")));
			while ((line = reader.readLine()) != null) {
				result.add(line); 			
			}
		} catch (FileNotFoundException e) {
			throw new FileNotFoundException("remove: Arquivo não encontrado.");
		} catch (IOException e) {
			throw new FileNotFoundException("remove: Erro ao escrever no arquivo de " + type);
		} finally {
			try {
				if (reader != null)				
					reader.close();
			} catch (IOException e) {
				throw new FileNotFoundException("remove: Erro ao fechar aquivo " + type);
			}		
		}	
		
		return result;
	}	
}
